/*
    sound.c --
    Sound emulation.
*/
#include "shared.h"
#include "config.h"

snd_t snd;
static int16 **fm_buffer;
static int16 **psg_buffer;
int *smptab;
int smptab_len;

void sound_mixer_ngc (int length);

int sound_init(void)
{
  uint8 *buf = NULL;
  int restore_fm = 0;
  int i;

  snd.fm_which = sms_option.fm_which;
  snd.fps = (sms.display == DISPLAY_NTSC) ? FPS_NTSC : FPS_PAL;
  snd.fm_clock = (sms.display == DISPLAY_NTSC) ? CLOCK_NTSC : CLOCK_PAL;
  snd.psg_clock = (sms.display == DISPLAY_NTSC) ? CLOCK_NTSC : CLOCK_PAL;
  snd.sample_rate = sms_option.sndrate;
  snd.mixer_callback = NULL;
  
  /* Save register settings */
  if (snd.enabled && sms.use_fm)
  {
      restore_fm = 1;
      buf = malloc (FM_GetContextSize ());
      FM_GetContext (buf);
  }

  /* If we are reinitializing, shut down sound emulation */
  if(snd.enabled)
  {
      sound_shutdown();
  }
 
  /* Disable sound until initialization is complete */
  snd.enabled = 0;

  /* Check if sample rate is invalid */
  if (snd.sample_rate < 8000 || snd.sample_rate > 48000)
    return 0;

  /* Assign stream mixing callback if none provided */
  if(!snd.mixer_callback)
  	  snd.mixer_callback = sound_mixer_callback;

  /* Calculate number of samples generated per frame */
  snd.sample_count = (snd.sample_rate / snd.fps);

  /* Calculate size of sample buffer */
  snd.buffer_size = snd.sample_count * 2;

  /* Free sample buffer position table if previously allocated */
  if (smptab)
    {
      free (smptab);
      smptab = NULL;
    }

  /* Prepare incremental info */
  snd.done_so_far = 0;
  smptab_len = (sms.display == DISPLAY_NTSC) ? 262 : 313;
  smptab = malloc (smptab_len * sizeof (int));
  if (!smptab) return 0;
  for (i = 0; i < smptab_len; i++)
    {
      double calc = (snd.sample_count * i);
      calc = calc / (double) smptab_len;
      smptab[i] = (int) calc;
    }

  /* Allocate emulated sound streams */
  for (i = 0; i < STREAM_MAX; i++)
    {
      snd.stream[i] = malloc (snd.buffer_size);
      if(!snd.stream[i]) return 0;
      memset (snd.stream[i], 0, snd.buffer_size);
    }

  /* Allocate sound output streams */
  snd.output[0] = malloc (snd.buffer_size);
  snd.output[1] = malloc (snd.buffer_size);
  if(!snd.output[0] || !snd.output[1]) return 0;

  /* Set up buffer pointers */
  fm_buffer = (int16 **) & snd.stream[STREAM_FM_MO];
  psg_buffer = (int16 **) & snd.stream[STREAM_PSG_L];

  /* Set up SN76489 emulation */
  SN76489_Init (0, snd.psg_clock, snd.sample_rate);

  /* Set up YM2413 emulation */
  FM_Init ();

  /* Inform other functions that we can use sound */
  snd.enabled = 1;

  /* Restore YM2413 register settings */
  if (restore_fm)
    {
      FM_SetContext (buf);
    }

  return 1;
}


void sound_shutdown(void)
{
  int i;

  if (!snd.enabled)
    return;

  /* Free emulated sound streams */
  for (i = 0; i < STREAM_MAX; i++)
    {
      if (snd.stream[i])
	{
	  free (snd.stream[i]);
	  snd.stream[i] = NULL;
	}
    }

  /* Free sound output buffers */
  for (i = 0; i < 2; i++)
    {
      if (snd.output[i])
	{
	  free (snd.output[i]);
	  snd.output[i] = NULL;
	}
    }

  /* Shut down SN76489 emulation */
  SN76489_Shutdown ();

  /* Shut down YM2413 emulation */
  FM_Shutdown ();
}


void sound_reset(void)
{
  if (!snd.enabled)
    return;

  /* Reset SN76489 emulator */
  SN76489_Reset (0);

  /* Reset YM2413 emulator */
  FM_Reset ();
}


void sound_update(int line)
{
  int16 *fm[2], *psg[2];

  if (!snd.enabled)
    return;

  /* Finish buffers at end of frame */
  if (line == smptab_len - 1)
    {
      psg[0] = psg_buffer[0] + snd.done_so_far;
      psg[1] = psg_buffer[1] + snd.done_so_far;
      fm[0] = fm_buffer[0] + snd.done_so_far;
      fm[1] = fm_buffer[1] + snd.done_so_far;

      /* Generate SN76489 sample data */
      SN76489_Update (0, psg, snd.sample_count - snd.done_so_far);

      /* Generate YM2413 sample data */
      FM_Update (fm, snd.sample_count - snd.done_so_far);

      /* Mix streams into output buffer */
      sound_mixer_ngc (snd.sample_count);

      /* Reset */
      snd.done_so_far = 0;
    }
  else
    {
      int tinybit;

      tinybit = smptab[line] - snd.done_so_far;

      /* Do a tiny bit */
      psg[0] = psg_buffer[0] + snd.done_so_far;
      psg[1] = psg_buffer[1] + snd.done_so_far;
      fm[0] = fm_buffer[0] + snd.done_so_far;
      fm[1] = fm_buffer[1] + snd.done_so_far;

      /* Generate SN76489 sample data */
      SN76489_Update (0, psg, tinybit);

      /* Generate YM2413 sample data */
      FM_Update (fm, tinybit);

      /* Sum total */
      snd.done_so_far += tinybit;
    }
}

/* Generic FM+PSG stereo mixer callback */
void sound_mixer_callback(int16 **stream, int16 **output, int length)
{
  int i;
  for (i = 0; i < length; i++)
    {
      int16 temp = (fm_buffer[0][i] + fm_buffer[1][i]) / 2;
      output[0][i] = temp + psg_buffer[0][i];
      output[1][i] = temp + psg_buffer[1][i];
    }
}

/*** Nintendo Gamecube Mixer ***/
extern unsigned char soundbuffer[16][3840];
extern int mixbuffer;
int IsMixing = 0;

void sound_mixer_ngc (int length)
{
  int16 *sb = (int16 *) soundbuffer[mixbuffer];
  int16 temp;
 
  int i,l,r;

  IsMixing = 1;
  for (i = 0; i < length; i++)
  {
      temp = (fm_buffer[0][i] + fm_buffer[1][i]) / 2;

	  l = temp + psg_buffer[0][i];
	  r = temp + psg_buffer[1][i];

	  if (l > 32767) l = 32767;
	  else if (l < -32768) l = -32768;
	  if (r > 32767) r = 32767;
	  else if (r < -32768) r = -32768;
		
      *sb++ = l;
      *sb++ = r;
  }

  mixbuffer++;
  mixbuffer &= 0xf;

  IsMixing = 0;
}

/*--------------------------------------------------------------------------*/
/* Sound chip access handlers                                               */
/*--------------------------------------------------------------------------*/

void psg_stereo_w(int data)
{
  if (!snd.enabled)
    return;
  SN76489_GGStereoWrite (0, data);
}

void stream_update(int which, int position)
{
}


void psg_write(int data)
{
  if (!snd.enabled)
    return;
  SN76489_Write (0, data);
}

/*--------------------------------------------------------------------------*/
/* Mark III FM Unit / Master System (J) built-in FM handlers                */
/*--------------------------------------------------------------------------*/

int fmunit_detect_r(void)
{
  return sms.fm_detect;
}

void fmunit_detect_w(int data)
{
  sms.fm_detect = data;
}

void fmunit_write(int offset, int data)
{
  if (!snd.enabled || !sms.use_fm)
    return;

  FM_Write (offset, data);
}
